import * as React from "react";
import * as Othello from "../../../domain/othello";
import * as Vector from "../../../common/vector";
import * as LocationProps from "../../common/locationProps";
import * as Lazy from "../../common/lazy";

interface GameBoardProps {
    readonly board: Othello.Cell[][]
    readonly onCellClicked: (cell: Vector.Vector) => void
}

class GameBoard extends React.Component<GameBoardProps & Partial<LocationProps.LocationProps>> {
    componentDidMount() { this.updateCanvas(); }
    componentDidUpdate() { this.updateCanvas(); }

    render() {
        const prop = LocationProps.MergeClassNames(["game-board"], this.props);
        return (
            <div className={LocationProps.ClassNameString(prop)} id={this.props.id}>
                <canvas ref="canvas"
                    className="game-board-canvas"
                    onMouseMove={e => this.updateCanvas(this.positionForCanvas(Vector.Create(e.clientX, e.clientY)))} 
                    onMouseDown={e=> {
                        const pos = new Positions((this.refs.canvas as HTMLCanvasElement).clientWidth);
                        const cell = pos.Canvas2Board(this.positionForCanvas(Vector.Create(e.clientX,e.clientY)));
                        if(cell)
                            this.props.onCellClicked(cell);
                    }}/>
            </div >
        )
    }

    positionForCanvas(mousePosition: Vector.Vector) {
        const rect = (this.refs.canvas as HTMLCanvasElement).getBoundingClientRect();
        return Vector.Map(
            x => x - rect.left,
            y => y - rect.top,
            mousePosition
        );
    }

    updateCanvas(selectedCoord?: Vector.Vector) {
        const canvasBoard = new CanvasBoard(this.getCanvas());
        canvasBoard.Init();
        canvasBoard.DrawLines();
        canvasBoard.DrawCells(this.props.board);
        if (selectedCoord != null)
            canvasBoard.DarkenSelected(selectedCoord);
    }

    getCanvas(){
        const ctx = (this.refs.canvas as HTMLCanvasElement).getContext("2d");
        if (ctx == null) {
            alert("Not supported, please update your browser!");
            throw("Canvas 2d context not supported.");
        }
        return ctx;
    }
}

const LineWidth = 2;

class CanvasBoard {
    constructor(context: CanvasRenderingContext2D) {
        this.Context = context;
        this.Positions = new Positions(this.Context.canvas.clientWidth);
    }

    public Init() {
        this.Context.canvas.width = this.Context.canvas.clientWidth;
        this.Context.canvas.height =  this.Context.canvas.clientWidth;
    }

    public DrawLines() {
        this.Context.lineWidth = LineWidth;

        this.Positions.Lines.forEach(x => {
            this.DrawLine(
                Vector.Create(0, x),
                Vector.Create(this.Positions.CanvasSideLength, x));
            this.DrawLine(
                Vector.Create(x, 0),
                Vector.Create(x, this.Positions.CanvasSideLength));
        });
    }

    public DrawCells(board: Othello.Cell[][]) {
        board.forEach((r, ri) => r.forEach((c, ci) => {
            if (c != "Empty")
                this.DrawChess(Vector.Create(ri, ci), c);
        }));
    }

    public DarkenSelected(canvasPosition: Vector.Vector) {
        const boardPosition = this.Positions.Canvas2Board(canvasPosition);
        if (boardPosition == null)
            return;
        else {
            this.Context.lineWidth = 0;
            this.Context.fillStyle = "rgba(0, 0, 0, 0.4)";
            const top = boardPosition.y * (this.Positions.CellSideLength + LineWidth);
            const left = boardPosition.x * (this.Positions.CellSideLength + LineWidth);
            this.Context.fillRect(
                left,
                top,
                this.Positions.CellSideLength + LineWidth,
                this.Positions.CellSideLength + LineWidth)
        }
    }

    private DrawChess(cellPosition: Vector.Vector, chess: Othello.Chess) {
        this.Context.beginPath();
        const canvasPosition = this.Positions.Board2Canvas(cellPosition);
        this.Context.arc(
            canvasPosition.x,
            canvasPosition.y,
            this.Positions.Radius,
            0,
            2 * Math.PI);
        this.Context.fillStyle = chess == "White" ? '#EAEDED' : "black";
        this.Context.fill();
        this.Context.lineWidth = 0;
        this.Context.stroke();
    }

    private DrawLine(from: Vector.Vector, to: Vector.Vector) {
        this.Context.moveTo(from.x, from.y);
        this.Context.lineTo(to.x, to.y);
        this.Context.stroke();
    }

    private readonly Context: CanvasRenderingContext2D;
    private readonly Positions: Positions;
}

class Positions {
    constructor(canvasSideLength: number) {
        this.CanvasSideLength = canvasSideLength;
        this._CellSideLength = Lazy.Create(() => this.GetCellSideLength(this.CanvasSideLength));
        this._Lines = Lazy.Create(() => this.GetLines(this.CanvasSideLength));
        this._Radius = Lazy.Create(() => this.CellSideLength * 0.4);
    }

    public Board2Canvas(boardPosition: Vector.Vector) {
        const map = (x: number) => x * (LineWidth + this.CellSideLength) + LineWidth + this.CellSideLength / 2;
        return Vector.Map(
            map,
            map,
            boardPosition
        );
    }

    public Canvas2Board(canvasPosition: Vector.Vector): (Vector.Vector | undefined) {
        const section = [...Array(8).keys()].map(x => { return { Dowline: this.Lines[x], UpperLine: this.Lines[x + 1] }; });
        for (const vertial of section.keys())
            for (const horizontal of section.keys()) {
                if (
                    section[vertial].Dowline < canvasPosition.y &&
                    section[vertial].UpperLine > canvasPosition.y &&
                    section[horizontal].Dowline < canvasPosition.x &&
                    section[horizontal].UpperLine > canvasPosition.x)
                    return Vector.Create(horizontal, vertial);
            }

        return undefined;
    }

    private GetCellSideLength(boardSideLength: number) {
        return (boardSideLength - LineWidth) / 8 - LineWidth;
    }

    private GetLines(boardSideLength: number) {
        return [
            LineWidth / 2,
            ...[...Array(8).keys()].map(x => (x + 1) * (LineWidth + this.CellSideLength) + LineWidth / 2)
        ];
    }

    public readonly CanvasSideLength: number;
    private readonly _CellSideLength: Lazy.Type<number>;
    public get CellSideLength() { return this._CellSideLength.value; }
    private readonly _Lines: Lazy.Type<number[]>;
    public get Lines() { return this._Lines.value; }
    private readonly _Radius: Lazy.Type<number>;
    public get Radius() { return this._Radius.value; }
}

export default GameBoard;